package Utils;

import java.util.ArrayList;

import DataModel.Tupel;

/**
 * The class implements and ORF finder
 * @author Stefan Lorenz<br>
 * <br>26.11.2012
 */
public class OrfFinder {

    private int transTable;
    private ArrayList<String> startCodons;
    private ArrayList<String> stopCodons;

    public OrfFinder(int transtabl) {
	this.transTable = transtabl;
	this.startCodons = new ArrayList<String>();
	this.stopCodons = new ArrayList<String>();
	setStartStopCodons();
    }

    /**
     * Sets the start and stop codons of the given translation table
     */
    private void setStartStopCodons() {
	this.startCodons.clear();
	this.stopCodons.clear();
	switch (this.transTable) {
	case 1:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 2:
	    this.startCodons.add("ATG");
	    this.startCodons.add("ATA");
	    this.stopCodons.add("AGG");
	    this.stopCodons.add("AGA");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 3:
	    this.startCodons.add("AGA");
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 4:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 5:
	    this.startCodons.add("ATG");
	    this.startCodons.add("ATA");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 6:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    break;
	case 9:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 10:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 11:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    this.stopCodons.add("TGA");
	    break;
	case 12:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    this.stopCodons.add("TAA");
	    this.stopCodons.add("TAG");
	    break;
	case 13:
	    this.startCodons.add("ATG");
	    this.startCodons.add("ATA");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 14:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    break;
	case 15:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    this.stopCodons.add("TAA");
	    break;
	case 16:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    this.stopCodons.add("TAA");
	    break;
	case 21:
	    this.startCodons.add("ATG");
	    this.startCodons.add("ATA");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	case 22:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TGA");
	    this.stopCodons.add("TAA");
	    this.stopCodons.add("TCA");
	    break;
	case 24:
	    this.startCodons.add("ATG");
	    this.stopCodons.add("TAG");
	    this.stopCodons.add("TAA");
	    break;
	default:
	    break;
	}
    }

    /**
     * Searches for the longest ORF and returns the CDS positions as Tupel
     * @param seq Sequence to search ORF in
     * @param strand strand of the sequence
     * @return a tupel with the CDS positions
     */
    public Tupel getCDSPositions(String seq, String strand) {
	
	ArrayList<Tupel> tupelList = new ArrayList<Tupel>();
	ArrayList<Tupel> tupelList1 = new ArrayList<Tupel>();
	ArrayList<Tupel> tupelList2 = new ArrayList<Tupel>();

	if (strand.equals("-")) {
	    StringBuffer sb = new StringBuffer(seq);
	    seq = sb.reverse().toString();
	    String newString = "";
	    for (int i = 0; i < seq.length(); i++) {
		if (seq.charAt(i) == 'A') {
		    newString += "T";
		} else if (seq.charAt(i) == 'T') {
		    newString += "A";
		} else if (seq.charAt(i) == 'G') {
		    newString += "C";
		} else if (seq.charAt(i) == 'C') {
		    newString += "G";
		}
	    }
	    seq = newString;
	}

	// shift 0
	boolean seenStart = false;
	for (int i = 0; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList.get(tupelList.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}

	int longest = -1;
	int index = -1;
	for (int i = 0; i < tupelList.size(); i++) {
	    if (tupelList.get(i).getStop() - tupelList.get(i).getStart() > longest) {
		longest = tupelList.get(i).getStop() - tupelList.get(i).getStart();
		index = i;
	    }
	}

	// shift 1
	seenStart = false;
	for (int i = 1; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList1.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList1.get(tupelList1.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}
	int longest1 = -1;
	int index1 = -1;
	for (int i = 0; i < tupelList1.size(); i++) {
	    if (tupelList1.get(i).getStop() - tupelList1.get(i).getStart() > longest1) {
		longest1 = tupelList1.get(i).getStop() - tupelList1.get(i).getStart();
		index1 = i;
	    }
	}

	// shift 2
	seenStart = false;
	for (int i = 2; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList2.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList2.get(tupelList2.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}
	int longest2 = -1;
	int index2 = -1;
	for (int i = 0; i < tupelList2.size(); i++) {
	    if (tupelList2.get(i).getStop() - tupelList2.get(i).getStart() > longest2) {
		longest2 = tupelList2.get(i).getStop() - tupelList2.get(i).getStart();
		index2 = i;
	    }
	}

	// find longest ORF
	int l = Math.max(longest, Math.max(longest1, longest2));
	if (l == -1) {
	    return null;
	} else if (l == longest) {
	    return tupelList.get(index);
	} else if (l == longest1) {
	    return tupelList1.get(index1);
	} else if (l == longest2) {
	    return tupelList2.get(index2);
	}
	return new Tupel(-1, -1);
    }

    /**
     * Searches for the longest ORF and returns the sequence of the ORF
     * @param seq Sequence to search ORF in
     * @param strand strand of the sequence
     * @return the sequence of the ORF
     */
    public String getCDSSequence(String seq, String strand) {

	ArrayList<Tupel> tupelList = new ArrayList<Tupel>();
	ArrayList<Tupel> tupelList1 = new ArrayList<Tupel>();
	ArrayList<Tupel> tupelList2 = new ArrayList<Tupel>();

	if (strand.equals("-")) {
	    StringBuffer sb = new StringBuffer(seq);
	    seq = sb.reverse().toString();
	    String newString = "";
	    for (int i = 0; i < seq.length(); i++) {
		if (seq.charAt(i) == 'A') {
		    newString += "T";
		} else if (seq.charAt(i) == 'T') {
		    newString += "A";
		} else if (seq.charAt(i) == 'G') {
		    newString += "C";
		} else if (seq.charAt(i) == 'C') {
		    newString += "G";
		}
	    }
	    seq = newString;
	}

	// shift 0
	boolean seenStart = false;
	for (int i = 0; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList.get(tupelList.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}

	int longest = -1;
	int index = -1;
	for (int i = 0; i < tupelList.size(); i++) {
	    if (tupelList.get(i).getStop() - tupelList.get(i).getStart() > longest) {
		longest = tupelList.get(i).getStop() - tupelList.get(i).getStart();
		index = i;
	    }
	}

	// shift 1
	seenStart = false;
	for (int i = 1; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList1.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList1.get(tupelList1.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}
	int longest1 = -1;
	int index1 = -1;
	for (int i = 0; i < tupelList1.size(); i++) {
	    if (tupelList1.get(i).getStop() - tupelList1.get(i).getStart() > longest1) {
		longest1 = tupelList1.get(i).getStop() - tupelList1.get(i).getStart();
		index1 = i;
	    }
	}

	// shift 2
	seenStart = false;
	for (int i = 2; i < seq.length() - 2; i += 3) {
	    if (this.startCodons.contains(seq.substring(i, i + 3))) {
		if (!seenStart)
		    tupelList2.add(new Tupel(i, -1));
		seenStart = true;
	    } else if (this.stopCodons.contains(seq.substring(i, i + 3))) {
		if (seenStart)
		    tupelList2.get(tupelList2.size() - 1).setStop(i + 3);
		seenStart = false;
	    }
	}
	int longest2 = -1;
	int index2 = -1;
	for (int i = 0; i < tupelList2.size(); i++) {
	    if (tupelList2.get(i).getStop() - tupelList2.get(i).getStart() > longest2) {
		longest2 = tupelList2.get(i).getStop() - tupelList2.get(i).getStart();
		index2 = i;
	    }
	}

	// FIND LONGEST ORF
	int l = Math.max(longest, Math.max(longest1, longest2));
	if (l == -1) {
	    return null;
	} else if (l == longest) {
	    return seq.substring(tupelList.get(index).getStart(), tupelList.get(index).getStop());
	} else if (l == longest1) {
	    return seq.substring(tupelList1.get(index1).getStart(), tupelList1.get(index1).getStop());
	} else if (l == longest2) {
	    return seq.substring(tupelList2.get(index2).getStart(), tupelList2.get(index2).getStop());
	}
	return null;
    }
}